#include "Common.h"
#include "Utils/Logger/Logger.h"
#include "Utils/Util.h"
#include "CGShader.h"
#include "CGShaderProgram.h"
#include "Graphics/Effect.h"
#include "Graphics/OpenGL/OpenGLTexture.h"
#include "Cg/cgGL.h"

using namespace RayFrame;

void RayFrame::CGShaderProgram::internalCreate()
{
  m_cgVs = static_cast<CGShader*>(m_vs);
  m_cgPs = static_cast<CGShader*>(m_ps);

  CGEffect::GetCGContext();
  m_isOK = CGEffect::CheckCGError("Create CG Shader");
}

void RayFrame::CGShaderProgram::internalDestroy()
{
  assert(m_isOK);

  m_isOK = false;
}

void RayFrame::CGShaderProgram::Begin()
{
  if(m_vs)
  {
    if(g_renderer->GetRendererType() == Renderer::OPENGL)
    {
      cgGLEnableProfile(m_cgVs->m_cgProfile);
      cgGLBindProgram(m_cgVs->GetCGProgram());
    }
    else
      assert(false);   // TODO
    cgUpdateProgramParameters(m_cgVs->GetCGProgram());
  }
  if(m_ps)
  {
    if(g_renderer->GetRendererType() == Renderer::OPENGL)
    {
      cgGLEnableProfile(m_cgPs->m_cgProfile);
      cgGLBindProgram(m_cgPs->GetCGProgram());
    }
    else
      assert(false);   // TODO
    cgUpdateProgramParameters(m_cgPs->GetCGProgram());
  }

  if(g_renderer->GetRendererType() == Renderer::OPENGL)
  {
    for(ShaderTexVecIter iter = m_cgTextures.begin(); 
      iter != m_cgTextures.end();
      iter ++)
    {
      cgGLEnableTextureParameter(*iter);
    }
  }

#ifdef _DEBUG
  CheckError();
#endif
}

void RayFrame::CGShaderProgram::End()
{
  if(g_renderer->GetRendererType() == Renderer::OPENGL)
  {
    if(m_vs)
      cgGLDisableProfile(m_cgVs->m_cgProfile);
    if(m_ps)
      cgGLDisableProfile(m_cgPs->m_cgProfile);
  }
  else
    assert(false);   // TODO

#ifdef _DEBUG
  CheckError();
#endif
}

bool RayFrame::CGShaderProgram::BindImage( STRING varName, Texture* image, Shader::ShaderType type)
{
  if(g_renderer->GetRendererType() == Renderer::OPENGL)
  {
    OpenGLTexture* texture = static_cast<OpenGLTexture*>(image);
    assert(texture);

    CGparameter texParam = GetHandle(type, varName);
    cgGLSetTextureParameter(texParam, texture->GetTextureID());
    
    ShaderTexVecIter iter = std::find(m_cgTextures.begin(), m_cgTextures.end(), texParam);
    if(iter == m_cgTextures.end())
      m_cgTextures.push_back(texParam);
    return true;
  }
  else
  {
    assert(false);    // TODO
    return false;
  }
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, const int val, Shader::ShaderType type )
{
  cgSetParameter1i(GetHandle(type, name), val);
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, const float val, Shader::ShaderType type )
{
  cgSetParameter1f(GetHandle(type, name), val);
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, glm::vec2 val, Shader::ShaderType type )
{
  cgSetParameter2f(GetHandle(type, name), val.x, val.y);
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, glm::vec3 val, Shader::ShaderType type )
{
  cgSetParameter3f(GetHandle(type, name), val.x, val.y, val.z);
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, glm::vec4 val, Shader::ShaderType type )
{
  cgSetParameter4f(GetHandle(type, name), val.x, val.y, val.z, val.w);
}

void RayFrame::CGShaderProgram::SetShaderParam(STRING name, glm::mat4 val, Shader::ShaderType type )
{
  glm::mat4 m = glm::transpose(val);
  cgSetMatrixParameterfr(GetHandle(type, name), &m[0][0]);
}

Shader* RayFrame::CGShaderProgram::CreateVertexShader()
{
  Shader* s = new CGShader();
  s->SetShaderType(Shader::VERTEX_SHADER);
  InsertShader(EMPTY_STRING, s);

  return s;
}

Shader* RayFrame::CGShaderProgram::CreatePixelShader()
{
  Shader* s = new CGShader();
  s->SetShaderType(Shader::PIXEL_SHADER);
  InsertShader(EMPTY_STRING, s);

  return s;
}

RayFrame::CGShaderProgram::CGShaderProgram()
{
  m_cgVs = m_cgPs = NULL;
}

CGparameter RayFrame::CGShaderProgram::GetHandle(Shader::ShaderType type, STRING name )
{
  int handleIndex = (int)type;

  ShaderVarMapIter iter = m_handles[handleIndex].find(name);
  if(iter != m_handles[handleIndex].end())
    return iter->second;

  CGparameter param;
  if (type == Shader::VERTEX_SHADER)
    param = cgGetNamedParameter(m_cgVs->m_cgProgram, name.c_str());
  else
    param = cgGetNamedParameter(m_cgPs->m_cgProgram, name.c_str());

  m_handles[handleIndex][name] = param;

#ifdef _DEBUG
  CheckError();
#endif
  return param;
}

bool RayFrame::CGShaderProgram::CheckError()
{
  return CGEffect::CheckCGError();
}
